是五月呀!

Spring AOP类内部方法调用不拦截

使用Spring AOP过程中,可能遇到过下列奇怪问题:

  • 类内部方法间调用AOP不起作用
  • 类内部方法间调用事务不起作用
  • 内部类异步线程调用外层方法事务不起作用

由于Spring的事务底层也是用AOP实现的,因此,这些症状都可以归结为,类内部方法间调用AOP失效。

举个官网例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class SimplePojo implements Pojo {
public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}

The key thing to understand here is that the client code inside the main(..) method of the Main class has a reference to the proxy. This means that method calls on that object reference are calls on the proxy. As a result, the proxy can delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object (the SimplePojo, reference in this case), any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.

main方法中,我们为SimplePojo创建了一个代理对象,pojo是代理对象的引用。
当调用foo()方法时,其实是调用代理对象的方法,我们可以在代理对象中添加拦截器对方法进行增强。
注意,foo()方法内部调用this.bar()时,是在this引用上的,并非代理对象引用,因此,不会被拦截到,无法增强。

那怎么解决这个问题呢?

The best approach is to refactor your code such that the self-invocation does not happen.

官网推荐做法:干掉内部调用。。。

如果干不掉呢?

那就得跟Spring AOP强绑定起来:

1
2
3
4
5
6
7
8
9
10
11
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}

或者在一个bean里面,从Spring容器中手动获取bean进行调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Component("simplePojo")
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
getService().bar();
}
public void bar() {
// some logic...
}
public SimplePojo getService() {
return (SimplePojo) SpringContextUtil.getBean("simplePojo");
}
}
/**
* 保存Spring上下文
*/
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public SpringContextUtil() {
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
applicationContext = applicationContext;
}
/**
* 根据bean名称获取bean
*/
public static Object getBean(String name) throws BeansException {
return applicationContext.getBean(name);
}
}

还有一种方法是在bean中引入自己:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component("simplePojo")
public class SimplePojo implements Pojo {
@Resource
private SimplePojo simplePojo;
public void foo() {
// this works, but... gah!
simplePojo.bar();
}
public void bar() {
// some logic...
}
public SimplePojo getService() {
return (SimplePojo) SpringContextUtil.getBean("simplePojo");
}
}

但是这种方式容易产生循环依赖,没有使用。

还有一种方法,是使用AspectJ的AOP代替Spring AOP的代理方式。在编译器进行代码织入。但是自己用的比较少,这里不做介绍了。

参考:
5.8.1. Understanding AOP Proxies